{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "# 图像分类\n",
    "\n",
    "在此项目中，你将对 [CIFAR-10 数据集](https://www.cs.toronto.edu/~kriz/cifar.html) 中的图片进行分类。该数据集包含飞机、猫狗和其他物体。你需要预处理这些图片，然后用所有样本训练一个卷积神经网络。图片需要标准化（normalized），标签需要采用 one-hot 编码。你需要应用所学的知识构建卷积的、最大池化（max pooling）、丢弃（dropout）和完全连接（fully connected）的层。最后，你需要在样本图片上看到神经网络的预测结果。\n",
    "\n",
    "\n",
    "## 获取数据\n",
    "\n",
    "请运行以下单元，以下载 [CIFAR-10 数据集（Python版）](https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz)。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "\"\"\"\n",
    "DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE\n",
    "\"\"\"\n",
    "from urllib.request import urlretrieve\n",
    "from os.path import isfile, isdir\n",
    "from tqdm import tqdm\n",
    "import problem_unittests as tests\n",
    "import tarfile\n",
    "\n",
    "cifar10_dataset_folder_path = 'cifar-10-batches-py'\n",
    "\n",
    "# Use Floyd's cifar-10 dataset if present\n",
    "floyd_cifar10_location = '/input/cifar-10/python.tar.gz'\n",
    "if isfile(floyd_cifar10_location):\n",
    "    tar_gz_path = floyd_cifar10_location\n",
    "else:\n",
    "    tar_gz_path = 'cifar-10-python.tar.gz'\n",
    "\n",
    "class DLProgress(tqdm):\n",
    "    last_block = 0\n",
    "\n",
    "    def hook(self, block_num=1, block_size=1, total_size=None):\n",
    "        self.total = total_size\n",
    "        self.update((block_num - self.last_block) * block_size)\n",
    "        self.last_block = block_num\n",
    "\n",
    "if not isfile(tar_gz_path):\n",
    "    with DLProgress(unit='B', unit_scale=True, miniters=1, desc='CIFAR-10 Dataset') as pbar:\n",
    "        urlretrieve(\n",
    "            'https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz',\n",
    "            tar_gz_path,\n",
    "            pbar.hook)\n",
    "\n",
    "if not isdir(cifar10_dataset_folder_path):\n",
    "    with tarfile.open(tar_gz_path) as tar:\n",
    "        tar.extractall()\n",
    "        tar.close()\n",
    "\n",
    "\n",
    "tests.test_folder_path(cifar10_dataset_folder_path)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 探索数据\n",
    "\n",
    "该数据集分成了几部分／批次（batches），以免你的机器在计算时内存不足。CIFAR-10 数据集包含 5 个部分，名称分别为 `data_batch_1`、`data_batch_2`，以此类推。每个部分都包含以下某个类别的标签和图片：\n",
    "\n",
    "* 飞机\n",
    "* 汽车\n",
    "* 鸟类\n",
    "* 猫\n",
    "* 鹿\n",
    "* 狗\n",
    "* 青蛙\n",
    "* 马\n",
    "* 船只\n",
    "* 卡车\n",
    "\n",
    "了解数据集也是对数据进行预测的必经步骤。你可以通过更改 `batch_id` 和 `sample_id` 探索下面的代码单元。`batch_id` 是数据集一个部分的 ID（1 到 5）。`sample_id` 是该部分中图片和标签对（label pair）的 ID。\n",
    "\n",
    "问问你自己：“可能的标签有哪些？”、“图片数据的值范围是多少？”、“标签是按顺序排列，还是随机排列的？”。思考类似的问题，有助于你预处理数据，并使预测结果更准确。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "%matplotlib inline\n",
    "%config InlineBackend.figure_format = 'retina'\n",
    "\n",
    "import helper\n",
    "import numpy as np\n",
    "\n",
    "# Explore the dataset\n",
    "batch_id = 1\n",
    "sample_id = 5\n",
    "helper.display_stats(cifar10_dataset_folder_path, batch_id, sample_id)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 实现预处理函数\n",
    "\n",
    "### 标准化\n",
    "\n",
    "在下面的单元中，实现 `normalize` 函数，传入图片数据 `x`，并返回标准化 Numpy 数组。值应该在 0 到 1 的范围内（含 0 和 1）。返回对象应该和 `x` 的形状一样。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "def normalize(x):\n",
    "    \"\"\"\n",
    "    Normalize a list of sample image data in the range of 0 to 1\n",
    "    : x: List of image data.  The image shape is (32, 32, 3)\n",
    "    : return: Numpy array of normalize data\n",
    "    \"\"\"\n",
    "    # TODO: Implement Function\n",
    "    return None\n",
    "\n",
    "\n",
    "\"\"\"\n",
    "DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE\n",
    "\"\"\"\n",
    "tests.test_normalize(normalize)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### One-hot 编码\n",
    "\n",
    "和之前的代码单元一样，你将为预处理实现一个函数。这次，你将实现 `one_hot_encode` 函数。输入，也就是 `x`，是一个标签列表。实现该函数，以返回为 one_hot 编码的 Numpy 数组的标签列表。标签的可能值为 0 到 9。每次调用 `one_hot_encode` 时，对于每个值，one_hot 编码函数应该返回相同的编码。确保将编码映射保存到该函数外面。\n",
    "\n",
    "提示：不要重复发明轮子。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "def one_hot_encode(x):\n",
    "    \"\"\"\n",
    "    One hot encode a list of sample labels. Return a one-hot encoded vector for each label.\n",
    "    : x: List of sample Labels\n",
    "    : return: Numpy array of one-hot encoded labels\n",
    "    \"\"\"\n",
    "    # TODO: Implement Function\n",
    "    return None\n",
    "\n",
    "\n",
    "\"\"\"\n",
    "DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE\n",
    "\"\"\"\n",
    "tests.test_one_hot_encode(one_hot_encode)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 随机化数据\n",
    "\n",
    "之前探索数据时，你已经了解到，样本的顺序是随机的。再随机化一次也不会有什么关系，但是对于这个数据集没有必要。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 预处理所有数据并保存\n",
    "\n",
    "运行下方的代码单元，将预处理所有 CIFAR-10 数据，并保存到文件中。下面的代码还使用了 10% 的训练数据，用来验证。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "\"\"\"\n",
    "DON'T MODIFY ANYTHING IN THIS CELL\n",
    "\"\"\"\n",
    "# Preprocess Training, Validation, and Testing Data\n",
    "helper.preprocess_and_save_data(cifar10_dataset_folder_path, normalize, one_hot_encode)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 检查点\n",
    "\n",
    "这是你的第一个检查点。如果你什么时候决定再回到该记事本，或需要重新启动该记事本，你可以从这里开始。预处理的数据已保存到本地。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "\"\"\"\n",
    "DON'T MODIFY ANYTHING IN THIS CELL\n",
    "\"\"\"\n",
    "import pickle\n",
    "import problem_unittests as tests\n",
    "import helper\n",
    "\n",
    "# Load the Preprocessed Validation data\n",
    "valid_features, valid_labels = pickle.load(open('preprocess_validation.p', mode='rb'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 构建网络\n",
    "\n",
    "对于该神经网络，你需要将每层都构建为一个函数。你看到的大部分代码都位于函数外面。要更全面地测试你的代码，我们需要你将每层放入一个函数中。这样使我们能够提供更好的反馈，并使用我们的统一测试检测简单的错误，然后再提交项目。\n",
    "\n",
    ">**注意**：如果你觉得每周很难抽出足够的时间学习这门课程，我们为此项目提供了一个小捷径。对于接下来的几个问题，你可以使用 [TensorFlow Layers](https://www.tensorflow.org/api_docs/python/tf/layers) 或 [TensorFlow Layers (contrib)](https://www.tensorflow.org/api_guides/python/contrib.layers) 程序包中的类来构建每个层级，但是“卷积和最大池化层级”部分的层级除外。TF Layers 和 Keras 及 TFLearn 层级类似，因此很容易学会。\n",
    "\n",
    ">但是，如果你想充分利用这门课程，请尝试自己解决所有问题，不使用 TF Layers 程序包中的任何类。你依然可以使用其他程序包中的类，这些类和你在 TF Layers 中的类名称是一样的！例如，你可以使用 TF Neural Network 版本的 `conv2d` 类 [tf.nn.conv2d](https://www.tensorflow.org/api_docs/python/tf/nn/conv2d)，而不是 TF Layers 版本的 `conv2d` 类 [tf.layers.conv2d](https://www.tensorflow.org/api_docs/python/tf/layers/conv2d)。\n",
    "\n",
    "我们开始吧！\n",
    "\n",
    "\n",
    "### 输入\n",
    "\n",
    "神经网络需要读取图片数据、one-hot 编码标签和丢弃保留概率（dropout keep probability）。请实现以下函数：\n",
    "\n",
    "* 实现 `neural_net_image_input`\n",
    " * 返回 [TF Placeholder](https://www.tensorflow.org/api_docs/python/tf/placeholder)\n",
    " * 使用 `image_shape` 设置形状，部分大小设为 `None`\n",
    " * 使用 [TF Placeholder](https://www.tensorflow.org/api_docs/python/tf/placeholder) 中的 TensorFlow `name` 参数对 TensorFlow 占位符 \"x\" 命名\n",
    "* 实现 `neural_net_label_input`\n",
    " * 返回 [TF Placeholder](https://www.tensorflow.org/api_docs/python/tf/placeholder)\n",
    " * 使用 `n_classes` 设置形状，部分大小设为 `None`\n",
    " * 使用 [TF Placeholder](https://www.tensorflow.org/api_docs/python/tf/placeholder) 中的 TensorFlow `name` 参数对 TensorFlow 占位符 \"y\" 命名\n",
    "* 实现 `neural_net_keep_prob_input`\n",
    " * 返回 [TF Placeholder](https://www.tensorflow.org/api_docs/python/tf/placeholder)，用于丢弃保留概率\n",
    " * 使用 [TF Placeholder](https://www.tensorflow.org/api_docs/python/tf/placeholder) 中的 TensorFlow `name` 参数对 TensorFlow 占位符 \"keep_prob\" 命名\n",
    "\n",
    "这些名称将在项目结束时，用于加载保存的模型。\n",
    "\n",
    "注意：TensorFlow 中的 `None` 表示形状可以是动态大小。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "import tensorflow as tf\n",
    "\n",
    "def neural_net_image_input(image_shape):\n",
    "    \"\"\"\n",
    "    Return a Tensor for a batch of image input\n",
    "    : image_shape: Shape of the images\n",
    "    : return: Tensor for image input.\n",
    "    \"\"\"\n",
    "    # TODO: Implement Function\n",
    "    return None\n",
    "\n",
    "\n",
    "def neural_net_label_input(n_classes):\n",
    "    \"\"\"\n",
    "    Return a Tensor for a batch of label input\n",
    "    : n_classes: Number of classes\n",
    "    : return: Tensor for label input.\n",
    "    \"\"\"\n",
    "    # TODO: Implement Function\n",
    "    return None\n",
    "\n",
    "\n",
    "def neural_net_keep_prob_input():\n",
    "    \"\"\"\n",
    "    Return a Tensor for keep probability\n",
    "    : return: Tensor for keep probability.\n",
    "    \"\"\"\n",
    "    # TODO: Implement Function\n",
    "    return None\n",
    "\n",
    "\n",
    "\"\"\"\n",
    "DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE\n",
    "\"\"\"\n",
    "tf.reset_default_graph()\n",
    "tests.test_nn_image_inputs(neural_net_image_input)\n",
    "tests.test_nn_label_inputs(neural_net_label_input)\n",
    "tests.test_nn_keep_prob_inputs(neural_net_keep_prob_input)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 卷积和最大池化层\n",
    "\n",
    "卷积层级适合处理图片。对于此代码单元，你应该实现函数 `conv2d_maxpool` 以便应用卷积然后进行最大池化：\n",
    "\n",
    "* 使用 `conv_ksize`、`conv_num_outputs` 和 `x_tensor` 的形状创建权重（weight）和偏置（bias）。\n",
    "* 使用权重和 `conv_strides` 对 `x_tensor` 应用卷积。\n",
    " * 建议使用我们建议的间距（padding），当然也可以使用任何其他间距。\n",
    "* 添加偏置\n",
    "* 向卷积中添加非线性激活（nonlinear activation）\n",
    "* 使用 `pool_ksize` 和 `pool_strides` 应用最大池化\n",
    " * 建议使用我们建议的间距（padding），当然也可以使用任何其他间距。\n",
    "\n",
    "**注意**：对于**此层**，**请勿使用** [TensorFlow Layers](https://www.tensorflow.org/api_docs/python/tf/layers) 或 [TensorFlow Layers (contrib)](https://www.tensorflow.org/api_guides/python/contrib.layers)，但是仍然可以使用 TensorFlow 的 [Neural Network](https://www.tensorflow.org/api_docs/python/tf/nn) 包。对于所有**其他层**，你依然可以使用快捷方法。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "def conv2d_maxpool(x_tensor, conv_num_outputs, conv_ksize, conv_strides, pool_ksize, pool_strides):\n",
    "    \"\"\"\n",
    "    Apply convolution then max pooling to x_tensor\n",
    "    :param x_tensor: TensorFlow Tensor\n",
    "    :param conv_num_outputs: Number of outputs for the convolutional layer\n",
    "    :param conv_ksize: kernal size 2-D Tuple for the convolutional layer\n",
    "    :param conv_strides: Stride 2-D Tuple for convolution\n",
    "    :param pool_ksize: kernal size 2-D Tuple for pool\n",
    "    :param pool_strides: Stride 2-D Tuple for pool\n",
    "    : return: A tensor that represents convolution and max pooling of x_tensor\n",
    "    \"\"\"\n",
    "    # TODO: Implement Function\n",
    "    return None \n",
    "\n",
    "\n",
    "\"\"\"\n",
    "DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE\n",
    "\"\"\"\n",
    "tests.test_con_pool(conv2d_maxpool)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 扁平化层\n",
    "\n",
    "实现 `flatten` 函数，将 `x_tensor` 的维度从四维张量（4-D tensor）变成二维张量。输出应该是形状（*部分大小（Batch Size）*，*扁平化图片大小（Flattened Image Size）*）。快捷方法：对于此层，你可以使用 [TensorFlow Layers](https://www.tensorflow.org/api_docs/python/tf/layers) 或 [TensorFlow Layers (contrib)](https://www.tensorflow.org/api_guides/python/contrib.layers) 包中的类。如果你想要更大挑战，可以仅使用其他 TensorFlow 程序包。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "def flatten(x_tensor):\n",
    "    \"\"\"\n",
    "    Flatten x_tensor to (Batch Size, Flattened Image Size)\n",
    "    : x_tensor: A tensor of size (Batch Size, ...), where ... are the image dimensions.\n",
    "    : return: A tensor of size (Batch Size, Flattened Image Size).\n",
    "    \"\"\"\n",
    "    # TODO: Implement Function\n",
    "    return None\n",
    "\n",
    "\n",
    "\"\"\"\n",
    "DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE\n",
    "\"\"\"\n",
    "tests.test_flatten(flatten)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 完全连接的层\n",
    "\n",
    "实现 `fully_conn` 函数，以向 `x_tensor` 应用完全连接的层级，形状为（*部分大小（Batch Size）*，*num_outputs*）。快捷方法：对于此层，你可以使用 [TensorFlow Layers](https://www.tensorflow.org/api_docs/python/tf/layers) 或 [TensorFlow Layers (contrib)](https://www.tensorflow.org/api_guides/python/contrib.layers) 包中的类。如果你想要更大挑战，可以仅使用其他 TensorFlow 程序包。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "def fully_conn(x_tensor, num_outputs):\n",
    "    \"\"\"\n",
    "    Apply a fully connected layer to x_tensor using weight and bias\n",
    "    : x_tensor: A 2-D tensor where the first dimension is batch size.\n",
    "    : num_outputs: The number of output that the new tensor should be.\n",
    "    : return: A 2-D tensor where the second dimension is num_outputs.\n",
    "    \"\"\"\n",
    "    # TODO: Implement Function\n",
    "    return None\n",
    "\n",
    "\n",
    "\"\"\"\n",
    "DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE\n",
    "\"\"\"\n",
    "tests.test_fully_conn(fully_conn)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 输出层\n",
    "\n",
    "实现 `output` 函数，向 x_tensor 应用完全连接的层级，形状为（*部分大小（Batch Size）*，*num_outputs*）。快捷方法：对于此层，你可以使用 [TensorFlow Layers](https://www.tensorflow.org/api_docs/python/tf/layers) 或 [TensorFlow Layers (contrib)](https://www.tensorflow.org/api_guides/python/contrib.layers) 包中的类。如果你想要更大挑战，可以仅使用其他 TensorFlow 程序包。\n",
    "\n",
    "**注意**：该层级不应应用 Activation、softmax 或交叉熵（cross entropy）。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "def output(x_tensor, num_outputs):\n",
    "    \"\"\"\n",
    "    Apply a output layer to x_tensor using weight and bias\n",
    "    : x_tensor: A 2-D tensor where the first dimension is batch size.\n",
    "    : num_outputs: The number of output that the new tensor should be.\n",
    "    : return: A 2-D tensor where the second dimension is num_outputs.\n",
    "    \"\"\"\n",
    "    # TODO: Implement Function\n",
    "    return None\n",
    "\n",
    "\n",
    "\"\"\"\n",
    "DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE\n",
    "\"\"\"\n",
    "tests.test_output(output)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 创建卷积模型\n",
    "\n",
    "实现函数 `conv_net`， 创建卷积神经网络模型。该函数传入一批图片 `x`，并输出对数（logits）。使用你在上方创建的层创建此模型：\n",
    "\n",
    "* 应用 1、2 或 3 个卷积和最大池化层（Convolution and Max Pool layers）\n",
    "* 应用一个扁平层（Flatten Layer）\n",
    "* 应用 1、2 或 3 个完全连接层（Fully Connected Layers）\n",
    "* 应用一个输出层（Output Layer）\n",
    "* 返回输出\n",
    "* 使用 `keep_prob` 向模型中的一个或多个层应用 [TensorFlow 的 Dropout](https://www.tensorflow.org/api_docs/python/tf/nn/dropout)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "def conv_net(x, keep_prob):\n",
    "    \"\"\"\n",
    "    Create a convolutional neural network model\n",
    "    : x: Placeholder tensor that holds image data.\n",
    "    : keep_prob: Placeholder tensor that hold dropout keep probability.\n",
    "    : return: Tensor that represents logits\n",
    "    \"\"\"\n",
    "    # TODO: Apply 1, 2, or 3 Convolution and Max Pool layers\n",
    "    #    Play around with different number of outputs, kernel size and stride\n",
    "    # Function Definition from Above:\n",
    "    #    conv2d_maxpool(x_tensor, conv_num_outputs, conv_ksize, conv_strides, pool_ksize, pool_strides)\n",
    "    \n",
    "\n",
    "    # TODO: Apply a Flatten Layer\n",
    "    # Function Definition from Above:\n",
    "    #   flatten(x_tensor)\n",
    "    \n",
    "\n",
    "    # TODO: Apply 1, 2, or 3 Fully Connected Layers\n",
    "    #    Play around with different number of outputs\n",
    "    # Function Definition from Above:\n",
    "    #   fully_conn(x_tensor, num_outputs)\n",
    "    \n",
    "    \n",
    "    # TODO: Apply an Output Layer\n",
    "    #    Set this to the number of classes\n",
    "    # Function Definition from Above:\n",
    "    #   output(x_tensor, num_outputs)\n",
    "    \n",
    "    \n",
    "    # TODO: return output\n",
    "    return None\n",
    "\n",
    "\n",
    "\"\"\"\n",
    "DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE\n",
    "\"\"\"\n",
    "\n",
    "##############################\n",
    "## Build the Neural Network ##\n",
    "##############################\n",
    "\n",
    "# Remove previous weights, bias, inputs, etc..\n",
    "tf.reset_default_graph()\n",
    "\n",
    "# Inputs\n",
    "x = neural_net_image_input((32, 32, 3))\n",
    "y = neural_net_label_input(10)\n",
    "keep_prob = neural_net_keep_prob_input()\n",
    "\n",
    "# Model\n",
    "logits = conv_net(x, keep_prob)\n",
    "\n",
    "# Name logits Tensor, so that is can be loaded from disk after training\n",
    "logits = tf.identity(logits, name='logits')\n",
    "\n",
    "# Loss and Optimizer\n",
    "cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=y))\n",
    "optimizer = tf.train.AdamOptimizer().minimize(cost)\n",
    "\n",
    "# Accuracy\n",
    "correct_pred = tf.equal(tf.argmax(logits, 1), tf.argmax(y, 1))\n",
    "accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32), name='accuracy')\n",
    "\n",
    "tests.test_conv_net(conv_net)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 训练神经网络\n",
    "\n",
    "### 单次优化\n",
    "\n",
    "实现函数 `train_neural_network` 以进行单次优化（single optimization）。该优化应该使用 `optimizer` 优化 `session`，其中 `feed_dict` 具有以下参数：\n",
    "\n",
    "* `x` 表示图片输入\n",
    "* `y` 表示标签\n",
    "* `keep_prob` 表示丢弃的保留率\n",
    "\n",
    "每个部分都会调用该函数，所以 `tf.global_variables_initializer()` 已经被调用。\n",
    "\n",
    "注意：不需要返回任何内容。该函数只是用来优化神经网络。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "def train_neural_network(session, optimizer, keep_probability, feature_batch, label_batch):\n",
    "    \"\"\"\n",
    "    Optimize the session on a batch of images and labels\n",
    "    : session: Current TensorFlow session\n",
    "    : optimizer: TensorFlow optimizer function\n",
    "    : keep_probability: keep probability\n",
    "    : feature_batch: Batch of Numpy image data\n",
    "    : label_batch: Batch of Numpy label data\n",
    "    \"\"\"\n",
    "    # TODO: Implement Function\n",
    "    pass\n",
    "\n",
    "\n",
    "\"\"\"\n",
    "DON'T MODIFY ANYTHING IN THIS CELL THAT IS BELOW THIS LINE\n",
    "\"\"\"\n",
    "tests.test_train_nn(train_neural_network)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 显示数据\n",
    "\n",
    "实现函数 `print_stats` 以输出损失和验证准确率。使用全局变量 `valid_features` 和 `valid_labels` 计算验证准确率。使用保留率 `1.0` 计算损失和验证准确率（loss and validation accuracy）。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "def print_stats(session, feature_batch, label_batch, cost, accuracy):\n",
    "    \"\"\"\n",
    "    Print information about loss and validation accuracy\n",
    "    : session: Current TensorFlow session\n",
    "    : feature_batch: Batch of Numpy image data\n",
    "    : label_batch: Batch of Numpy label data\n",
    "    : cost: TensorFlow cost function\n",
    "    : accuracy: TensorFlow accuracy function\n",
    "    \"\"\"\n",
    "    # TODO: Implement Function\n",
    "    pass"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 超参数\n",
    "\n",
    "调试以下超参数：\n",
    "* 设置 `epochs` 表示神经网络停止学习或开始过拟合的迭代次数\n",
    "* 设置 `batch_size`，表示机器内存允许的部分最大体积。大部分人设为以下常见内存大小：\n",
    "\n",
    " * 64\n",
    " * 128\n",
    " * 256\n",
    " * ...\n",
    "* 设置 `keep_probability` 表示使用丢弃时保留节点的概率"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# TODO: Tune Parameters\n",
    "epochs = None\n",
    "batch_size = None\n",
    "keep_probability = None"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 在单个 CIFAR-10 部分上训练\n",
    "\n",
    "我们先用单个部分，而不是用所有的 CIFAR-10 批次训练神经网络。这样可以节省时间，并对模型进行迭代，以提高准确率。最终验证准确率达到 50% 或以上之后，在下一部分对所有数据运行模型。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "\"\"\"\n",
    "DON'T MODIFY ANYTHING IN THIS CELL\n",
    "\"\"\"\n",
    "print('Checking the Training on a Single Batch...')\n",
    "with tf.Session() as sess:\n",
    "    # Initializing the variables\n",
    "    sess.run(tf.global_variables_initializer())\n",
    "    \n",
    "    # Training cycle\n",
    "    for epoch in range(epochs):\n",
    "        batch_i = 1\n",
    "        for batch_features, batch_labels in helper.load_preprocess_training_batch(batch_i, batch_size):\n",
    "            train_neural_network(sess, optimizer, keep_probability, batch_features, batch_labels)\n",
    "        print('Epoch {:>2}, CIFAR-10 Batch {}:  '.format(epoch + 1, batch_i), end='')\n",
    "        print_stats(sess, batch_features, batch_labels, cost, accuracy)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 完全训练模型\n",
    "\n",
    "现在，单个 CIFAR-10 部分的准确率已经不错了，试试所有五个部分吧。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "\"\"\"\n",
    "DON'T MODIFY ANYTHING IN THIS CELL\n",
    "\"\"\"\n",
    "save_model_path = './image_classification'\n",
    "\n",
    "print('Training...')\n",
    "with tf.Session() as sess:\n",
    "    # Initializing the variables\n",
    "    sess.run(tf.global_variables_initializer())\n",
    "    \n",
    "    # Training cycle\n",
    "    for epoch in range(epochs):\n",
    "        # Loop over all batches\n",
    "        n_batches = 5\n",
    "        for batch_i in range(1, n_batches + 1):\n",
    "            for batch_features, batch_labels in helper.load_preprocess_training_batch(batch_i, batch_size):\n",
    "                train_neural_network(sess, optimizer, keep_probability, batch_features, batch_labels)\n",
    "            print('Epoch {:>2}, CIFAR-10 Batch {}:  '.format(epoch + 1, batch_i), end='')\n",
    "            print_stats(sess, batch_features, batch_labels, cost, accuracy)\n",
    "            \n",
    "    # Save Model\n",
    "    saver = tf.train.Saver()\n",
    "    save_path = saver.save(sess, save_model_path)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 检查点\n",
    "\n",
    "模型已保存到本地。\n",
    "\n",
    "## 测试模型\n",
    "\n",
    "利用测试数据集测试你的模型。这将是最终的准确率。你的准确率应该高于 50%。如果没达到，请继续调整模型结构和参数。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "\"\"\"\n",
    "DON'T MODIFY ANYTHING IN THIS CELL\n",
    "\"\"\"\n",
    "%matplotlib inline\n",
    "%config InlineBackend.figure_format = 'retina'\n",
    "\n",
    "import tensorflow as tf\n",
    "import pickle\n",
    "import helper\n",
    "import random\n",
    "\n",
    "# Set batch size if not already set\n",
    "try:\n",
    "    if batch_size:\n",
    "        pass\n",
    "except NameError:\n",
    "    batch_size = 64\n",
    "\n",
    "save_model_path = './image_classification'\n",
    "n_samples = 4\n",
    "top_n_predictions = 3\n",
    "\n",
    "def test_model():\n",
    "    \"\"\"\n",
    "    Test the saved model against the test dataset\n",
    "    \"\"\"\n",
    "\n",
    "    test_features, test_labels = pickle.load(open('preprocess_test.p', mode='rb'))\n",
    "    loaded_graph = tf.Graph()\n",
    "\n",
    "    with tf.Session(graph=loaded_graph) as sess:\n",
    "        # Load model\n",
    "        loader = tf.train.import_meta_graph(save_model_path + '.meta')\n",
    "        loader.restore(sess, save_model_path)\n",
    "\n",
    "        # Get Tensors from loaded model\n",
    "        loaded_x = loaded_graph.get_tensor_by_name('x:0')\n",
    "        loaded_y = loaded_graph.get_tensor_by_name('y:0')\n",
    "        loaded_keep_prob = loaded_graph.get_tensor_by_name('keep_prob:0')\n",
    "        loaded_logits = loaded_graph.get_tensor_by_name('logits:0')\n",
    "        loaded_acc = loaded_graph.get_tensor_by_name('accuracy:0')\n",
    "        \n",
    "        # Get accuracy in batches for memory limitations\n",
    "        test_batch_acc_total = 0\n",
    "        test_batch_count = 0\n",
    "        \n",
    "        for test_feature_batch, test_label_batch in helper.batch_features_labels(test_features, test_labels, batch_size):\n",
    "            test_batch_acc_total += sess.run(\n",
    "                loaded_acc,\n",
    "                feed_dict={loaded_x: test_feature_batch, loaded_y: test_label_batch, loaded_keep_prob: 1.0})\n",
    "            test_batch_count += 1\n",
    "\n",
    "        print('Testing Accuracy: {}\\n'.format(test_batch_acc_total/test_batch_count))\n",
    "\n",
    "        # Print Random Samples\n",
    "        random_test_features, random_test_labels = tuple(zip(*random.sample(list(zip(test_features, test_labels)), n_samples)))\n",
    "        random_test_predictions = sess.run(\n",
    "            tf.nn.top_k(tf.nn.softmax(loaded_logits), top_n_predictions),\n",
    "            feed_dict={loaded_x: random_test_features, loaded_y: random_test_labels, loaded_keep_prob: 1.0})\n",
    "        helper.display_image_predictions(random_test_features, random_test_labels, random_test_predictions)\n",
    "\n",
    "\n",
    "test_model()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 为何准确率只有50-80%？\n",
    "\n",
    "你可能想问，为何准确率不能更高了？首先，对于简单的 CNN 网络来说，50% 已经不低了。纯粹猜测的准确率为10%。但是，你可能注意到有人的准确率[远远超过 80%](http://rodrigob.github.io/are_we_there_yet/build/classification_datasets_results.html#43494641522d3130)。这是因为我们还没有介绍所有的神经网络知识。我们还需要掌握一些其他技巧。\n",
    "\n",
    "## 提交项目\n",
    "\n",
    "提交项目时，确保先运行所有单元，然后再保存记事本。将 notebook 文件另存为“dlnd_image_classification.ipynb”，再在目录 \"File\" -> \"Download as\" 另存为 HTML 格式。请在提交的项目中包含 “helper.py” 和 “problem_unittests.py” 文件。\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
